
;任务切换的程序

;注意事项

;程序要加上 bits 32(不加的话，默认按照16位编译)
;比较字符串，用地址来替换符号，要再仔细调试，之前没调通就是这里有点小问题
bits 32;一定要加上这个，编译器默认是按照16位的代码翻译的

;头部
section header vstart=0
				selector_sys_routine equ 0000_0000_00101_000b ;系统调用段的选择子，在gdt中的索引是5
				selector_core_data equ 0000_0000_00110_000b ;核心数据段的选择子，在gdt中的索引是6
				selector_core_code equ 0000_0000_00111_000b ;核心代码段的选择子,在gdt中的索引是7
				selector_core_stack equ 0000_0000_00011_000b ;核心栈段的选择子，在gdt中的索引是3
				selector_0_4_gb equ 0000_0000_00001_000b ;能访问全部4GB内存的选择子，在全部4GB中能读能写,在gdt中的索引是1
				selector_graphic equ 0000_0000_00100_000b ;输入到显示缓冲区的选择子，在gdt中的索引是4

				user_program_sector equ 80 ;用户程序在硬盘上面的逻辑扇区号

				program_size 		dd end_ ;程序的大小
				seg_sys_routine		dd section.sys_routine.start ;系统调用段的汇编地址
				seg_core_data		dd section.core_data.start ;核心数据段的汇编地址
				seg_core_code		dd section.core_code.start ;核心代码段的汇编地址
				entry_start			dd start
									dw selector_core_code







;==================================================系统调用段开始==================================================
section sys_routine vstart=0

;-------------------------------------------------------------------------------
;从硬盘读取一个逻辑扇区
;EAX=逻辑扇区号
;DS:EBX=目标缓冲区地址
;返回：EBX=EBX+512
read_hard_disk_0:
				push edx
				push ecx
				push eax

				mov ecx, eax
				;设置读取的扇区数量，端口号0x1f2, 端口长度是8位
				mov dx, 0x1f2
				mov al, 1
				out dx, al

				;设置起始的扇区号 端口号分别是0x1f3,0x1f4,0x1f5,x01f6,依次存放0-7, 8-15, 16- 23, 24-27位
				inc dx
				mov al, cl
				out dx, al

				inc dx
				mov al, ch
				out dx, al

				inc dx
				shr ecx, 16
				mov al, cl
				out dx, al

				inc dx
				and ch, 0x0f ;高4位置0
				or ch, 0xe0 ;高4位设置成1110,LBA模式，主硬盘
				mov al, ch
				out dx, al

				;请求硬盘读，端口号0x1f7，8位端口
				inc dx
				mov al, 0x20
				out dx, al

				;等待读操作完成,端口号也是0x1f7
waits:			in al, dx
				and al, 0x88
				cmp al, 0x08
				jne waits

				;连续读出数据,端口号0x1f0, 16位端口
				mov dx, 0x1f0
				mov ecx, 256 ;循环次数,不能使用cx
read_lp:		in ax, dx
				mov [ebx], ax
				add ebx, 2
				loop read_lp


				pop eax
				pop ecx
				pop edx
				retf


;-------------------------------------------------------------------------------
;分配内存
;输入：ECX=希望分配的字节数
;输出：ECX=起始线性地址 
allocate_memory:
				push eax
				push ebx
				push ds

				mov eax, selector_core_data
				mov ds, eax
				mov eax, ecx ;希望分配的字节数
				mov ecx, [ram_alloc] ;返回起始的线性地址,返回值

				;为下一次分配做准备，地址是4字节对齐的，也就是4的倍数
				add eax, ecx ;下一次的线性地址
				mov ebx, eax
				and ebx, 0xfffffffc ;c的二进制为1100
				add ebx, 4 ;ebx是4字节对齐的
				test eax, 0x00000003 ;3的二级制是0011
				cmovnz eax, ebx ;not zero才传送， cmovnz是条件传送指令
				mov [ram_alloc], eax

				pop ds
				pop ebx
				pop eax
				retf


;-------------------------------------------------------------------------------
;构造存储器和系统的段描述符
;输入：EAX=线性基地址
;      EBX=段界限
;      ECX=属性。各属性位都在原始
;          位置，无关的位清零 
;返回：EDX:EAX=描述符
make_seg_descriptor:
				mov edx, eax
				;构造描述符的低32位
				shl eax, 16
				or ax, bx 

				;构造描述符的高32位
				and edx, 0xffff0000
				rol edx, 8
				bswap edx
				xor bx, bx ;将bx置0,ebx只有16-19位有效，其他位均为0
				or edx, ebx
				or edx, ecx

				retf



;-------------------------------------------------------------------------------
;在GDT内安装一个新的描述符
;输入：EDX:EAX=描述符 
;输出：CX=描述符的选择子
set_up_gdt_descriptor:
				push ebx
				push ds
				push es
				push edi

				mov ebx, selector_core_data
				mov ds, ebx
				mov ebx, selector_0_4_gb
				mov es, ebx
				sgdt [pgdt]
				movzx ebx, word [pgdt] ;获取界限
				inc bx
				mov edi, [pgdt + 2] ;获取gdt的地址
				mov [es:edi + ebx], eax
				mov [es:edi + ebx + 4], edx
				add word [pgdt], 8 ;重新设置gdt的界限
				lgdt [pgdt] ;重新加载gdt

				;计算安装的描述符的选择子
				movzx eax, word [pgdt]
				mov edx, 0
				mov ebx, 8
				div ebx
				mov cx, ax
				shl cx, 3
				
				pop edi
				pop es
				pop ds
				pop ebx
				retf				



;-------------------------------------------------------------------------------
make_gate_descriptor:                       ;构造门的描述符（调用门等）
                                            ;输入：EAX=门代码在段内偏移地址
                                            ;       BX=门代码所在段的选择子 
                                            ;       CX=段类型及属性等（各属
                                            ;          性位都在原始位置）
                                            ;返回：EDX:EAX=完整的描述符

                    mov edx, eax
                    ;创建门描述符的低16位   
                    and eax, 0x0000ffff
                    shl ebx, 16
                    or eax, ebx

                    ;创建门描述符的高16位
                    and edx, 0xffff0000
                    and ecx, 0x0000ffff
                    or edx, ecx

                    retf



;-------------------------------------------------------------------------------
terminate_current_task:                     ;终止当前任务
                                            ;注意，执行此例程时，当前任务仍在
                                            ;运行中。此例程其实也是当前任务的
                                            ;一部分 
                            mov edx, selector_core_data 
                            mov ds, edx  

                            pushfd
                            pop edx

                            test dx, 0100_0000_0000_0000b ;判断NT位
                            jnz tct_1
                            mov ebx, core_msg1
                            call selector_sys_routine:put_string
                            jmp far [prgman_tss] ;NT位等于0，通过jmp进行任务切换


tct_1:						mov ebx, core_msg0
							call selector_sys_routine:put_string
							iretd ;NT位等于1，通过iret来切换任务



;-------------------------------------------------------------------------------
;字符串显示例程
;显示0终止的字符串并移动光标 
;输入：DS:EBX=串地址
put_string:                                 
         push ecx
  .getc:
         mov cl,[ebx]
         or cl,cl
         jz .exit
         call put_char
         inc ebx
         jmp .getc

  .exit:
         pop ecx
         retf                               ;段间返回


;-------------------------------------------------------------------------------
put_char:                                   ;在当前光标处显示一个字符,并推进
                                            ;光标。仅用于段内调用 
                                            ;输入：CL=字符ASCII码 
         pushad

         ;以下取当前光标位置
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         in al,dx                           ;高字
         mov ah,al

         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         in al,dx                           ;低字
         mov bx,ax                          ;BX=代表光标位置的16位数

         cmp cl,0x0d                        ;回车符？
         jnz .put_0a
         mov ax,bx
         mov bl,80
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

  .put_0a:
         cmp cl,0x0a                        ;换行符？
         jnz .put_other
         add bx,80
         jmp .roll_screen

  .put_other:                               ;正常显示字符
         push es
         mov eax, selector_graphic          ;0xb8000段的选择子
         mov es,eax
         shl bx,1
         mov [es:bx],cl
         pop es

         ;以下将光标位置推进一个字符
         shr bx,1
         inc bx

  .roll_screen:
         cmp bx,2000                        ;光标超出屏幕？滚屏
         jl .set_cursor

         push ds
         push es
         mov eax, selector_graphic
         mov ds,eax
         mov es,eax
         cld
         mov esi,0xa0                       ;小心！32位模式下movsb/w/d 
         mov edi,0x00                       ;使用的是esi/edi/ecx 
         mov ecx,1920
         rep movsd
         mov bx,3840                        ;清除屏幕最底一行
         mov ecx,80                         ;32位程序应该使用ECX
  .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         pop es
         pop ds

         mov bx,1920

  .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         mov al,bh
         out dx,al
         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         mov al,bl
         out dx,al

         popad
         ret  


;-------------------------------------------------------------------------------
;汇编语言程序是极难一次成功，而且调试非常困难。这个例程可以提供帮助 
put_hex_dword:                              ;在当前光标处以十六进制形式显示
                                            ;一个双字并推进光标 
                                            ;输入：EDX=要转换并显示的数字
                                            ;输出：无
         pushad
         push ds
      
         mov ax, selector_core_data          ;切换到核心数据段 
         mov ds,ax
      
         mov ebx,bin_hex                    ;指向核心数据段内的转换表
         mov ecx,8
  .xlt:    
         rol edx,4
         mov eax,edx
         and eax,0x0000000f
         xlat
      
         push ecx
         mov cl,al                           
         call put_char
         pop ecx
       
         loop .xlt
      
         pop ds
         popad
         retf


;==================================================系统调用段结束==================================================




;==================================================核心数据段开始==================================================
section core_data vstart=0

			ram_alloc						dd 0x00100000 ;下次分配内存的起始地址
			pgdt							dw 0 ;gdt界限
											dd 0 ;gdt基址
			msg_ret_from_user_program		db 0x0d, 0x0a, 'back from user program, all down!', 0x0d,0x0a, 0	
			buf								times 2048 db 0 ;一块比较大的内存，作为临时缓冲区	

			;符号表
			salt							
			salt_items						dd (salt_end - salt_1) / 262 ;符号表的条目数
			salt_1							db '@PrintString'
											times 256-($-salt_1) db 0
											dd put_string
											dw selector_sys_routine

			salt_2							db '@ReadDiskData'
											times 256-($-salt_2) db 0
											dd read_hard_disk_0
											dw selector_sys_routine	

			salt_3							db '@PutHexDword'
											times 256-($-salt_3) db 0
											dd put_hex_dword
											dw selector_sys_routine

			salt_4							db '@TerminateProgram'	
											times 256-($-salt_4) db 0
											dd terminate_current_task	
											dw selector_sys_routine
			salt_end						nop

											;cpu品牌	
			cpu_brand						times 64 db 0
			bin_hex          				db '0123456789ABCDEF'
                                            
                                            ;tcb链的首地址
            tcb_chain                       dd 0
            gate_install_success			db 'gate install success!!!', 0	


            ;程序管理器的任务信息 
	         prgman_tss       dd  0             ;程序管理器的TSS基地址
	                          dw  0             ;程序管理器的TSS描述符选择子 

	         prgman_msg1      db  0x0d,0x0a
	                          db  '[PROGRAM MANAGER]: Hello! I am Program Manager,'
	                          db  'run at CPL=0.Now,create user task and switch '
	                          db  'to it by the CALL instruction...',0x0d,0x0a,0
	                 
	         prgman_msg2      db  0x0d,0x0a
	                          db  '[PROGRAM MANAGER]: I am glad to regain control.'
	                          db  'Now,create another user task and switch to '
	                          db  'it by the JMP instruction...',0x0d,0x0a,0
	                 
	         prgman_msg3      db  0x0d,0x0a
	                          db  '[PROGRAM MANAGER]: I am gain control again,'
	                          db  'HALT...',0

	         core_msg0        db  0x0d,0x0a
	                          db  '[SYSTEM CORE]: Uh...This task initiated with '
	                          db  'CALL instruction or an exeception/ interrupt,'
	                          db  'should use IRETD instruction to switch back...'
	                          db  0x0d,0x0a,0

	         core_msg1        db  0x0d,0x0a
	                          db  '[SYSTEM CORE]: Uh...This task initiated with '
	                          db  'JMP instruction,  should switch to Program '
	                          db  'Manager directly by the JMP instruction...'
	                          db  0x0d,0x0a,0											


;==================================================核心数据段结束==================================================





;==================================================核心代码段开始==================================================
section core_code vstart=0

;-------------------------------------------------------------------------------
;加载并重定位用户程序
;输入: PUSH 逻辑扇区号
;      PUSH 任务控制块基地址
;输出：无
load_relocate_program:	
						pushad
						push ds
						push es

						mov eax, selector_0_4_gb
						mov es, eax

						mov ebp, esp
						mov esi, [ebp + 11 * 4] ;tcb的基地址

						;给ldt分配空间
						mov ecx, 160 ;160 = 8 * 20,总共是20个描述符
						call selector_sys_routine:allocate_memory
						mov [es:esi + 0x0c], ecx ;ldt的基地址填写到tcb中
						mov word [es:esi + 0x0a], 0xffff ;ldt的界限填写到tcb中,注意ldt的初始界限的设置，加1之后越界，正好是0
						
						
						;读取用户程序的第一个扇区
						mov ebx, selector_core_data
						mov ds, ebx
						mov ebx, buf
						mov eax, [ebp + 12 * 4] ;用户程序在磁盘上的逻辑扇区号
						call selector_sys_routine:read_hard_disk_0
						mov eax, [buf] ;获取用户程序的总长度

						;让eax中的值是512字节对齐，也就是512的倍数
						mov ebx, eax
						and ebx, 0xfffffe00 ;e00的二进制为1110_0000_0000
						add ebx, 512
						test eax, 0x000001ff ;1ff的二进制位0001_1111_1111
						cmovnz eax, ebx

						;给用户程序在内存中分配空间
						mov ecx, eax
						call selector_sys_routine:allocate_memory
						mov edi, ecx ;用户程序的起始线性地址
						mov [es:esi + 0x06], ecx ;将用户程序的起始地址填写到tcb中
						

						;将用户程序从硬盘上读取到内存中,此时eax中的值是512的整数倍。
						mov edx, 0
						mov ebx, 512
						div ebx
						mov ecx, eax ;商，也就是用户程序占据的扇区的个数, 也就是循环次数
						mov eax, [ebp + 12 * 4] ;起始逻辑扇区号
						mov ebx, selector_0_4_gb
						mov ds, ebx
						mov ebx, edi;用户程序的起始线性地址
load_user_lp:			call selector_sys_routine:read_hard_disk_0
						inc eax ;逻辑扇区号加1
						loop load_user_lp


						;重定位用户程序
						;重定位用户程序头部段
						mov eax, edi
						mov ebx, [edi + 0x04] ;头部段的长度
						dec ebx
						mov ecx, 0x0040F200 ;粒度是1字节，数据段，可读可写,DPL = 3
						call selector_sys_routine:make_seg_descriptor ;生成描述符
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt ;安装描述符
						mov [edi + 0x04], cx ;将生成的选择子写到用户程序头部的描述头部段汇编地址的地方
						mov [esi + 0x44], cx ;将生成的选择子填写到tcb中

						;重定位用户程序代码段
						mov eax, [edi + 0x14] ;代码段的汇编地址
						add eax, edi ;代码段在内存中的基址
						mov ebx, [edi + 0x18] ;代码段的长度
						dec ebx
						mov ecx, 0x0040F800 ;粒度是1字节，代码段，只执行，不可读,DPL = 3
						call selector_sys_routine:make_seg_descriptor ;生成描述符
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt ;安装描述符
						mov [edi + 0x14], cx ;将生成的选择子写到用户程序头部的描述代码段汇编地址的地方

						;重定位用户程序数据段
						mov eax, [edi + 0x1c] ;数据段的汇编地址
						add eax, edi ;数据段在内存中的基址
						mov ebx, [edi + 0x20] ;数据段的长度
						dec ebx
						mov ecx, 0x0040F200 ;粒度是1字节，数据段，可读可写,DPL = 3
						call selector_sys_routine:make_seg_descriptor;生成描述符
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt ;安装描述符
						mov [edi + 0x1c], cx ;将生成的选择子写到用户程序头部的描述数据段汇编地址的地方

						;重定位用户程序栈段
						mov eax, [edi + 0x0c] ;用户程序希望的栈大小，以4KB为单位
						mov ebx, 0xfffff
						sub ebx, eax ;段界限,如果eax是1，则段界限是0xffffe,实际的段界限是0xffffefff
						mov edx, 0
						mov ecx, 4096
						mul ecx
						mov ecx, eax ;用户程序希望的栈大小，单位是字节
						call selector_sys_routine:allocate_memory
						add eax, ecx ;为用户程序分配的栈的首地址 加上栈的大小,栈是从高到低的
						mov ecx, 0x00c0F600 ;粒度是4KB，栈段，可读可写,DPL = 3
						call selector_sys_routine:make_seg_descriptor ;生成描述符
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt ;安装描述符
						mov [edi + 0x08], cx ;将生成的选择子写到用户程序头部的描述栈段汇编地址的地方


						;将用户程序使用的函数名，替换成地址
						;push edi ;保存用户程序的首地址
						mov ecx, [edi + 0x24] ;用户程序的符号表的条目个数
						mov eax, selector_core_data
						mov es, eax ;es中为核心数据段的选择子
						;mov esi, edi + 0x28 ;比较的源字符串 ds:esi
						mov esi, edi ;比较的源字符串 ds:esi
						add esi, 0x28
						mov edi, salt_1 ;比较的目的字符串 es:edi
						

;外部循环，要循环的次数 = 用户程序的符号表的条目个数
out_1:					push edi
						push ecx
						mov ecx, [es:salt_items] ;核心数据段的条目数


;内部循环，要循环的次数 = 核心数据段的条目数
in_1:					push esi ;在用户程序的某个符号和核心数据段的所有条目进行比对的过程中，用户程序的索引不能变
						push ecx
						mov ecx, 256
						cld
						repe cmpsb
						jne in_1_c
						mov ebx, [es:edi] ;偏移,低位
						mov [esi-256], ebx
						mov bx, [es:edi + 4] ;选择子，高位
						mov [esi-252], bx

in_1_c:					mov ebx, 6
						add ebx, ecx
						add edi, ebx ;核心数据段条目的索引增加的值 = (6 + ecx),完整的循环完成，ecx = 0,加6就可以。只循环1次，ecx = 255，需要加上255 + 6
						pop ecx
						pop esi
						loop in_1


						add esi, 256 ;指向用户程序符号表的下一个条目
						pop ecx
						pop edi
						loop out_1


						mov esi, [ebp + 11 * 4] ;tcb的基地址
						;创建0特权级的栈
						mov ecx, 4096
						mov dword [esi + 0x1a], 1 ;0特权级栈的大小(以4kb为单位)填写到tcb中
						call selector_sys_routine:allocate_memory
						add ecx, 4096 ;栈的高端地址
						mov [esi + 0x1e], ecx ;0特权级栈的地址填写到tcb中
						mov dword [esi + 0x24], 0 ;0特权级栈初始的esp填写到tcb中
						mov eax, ecx ;基地址
						mov ebx, 0xfffff
						sub ebx, 1 ;0xffffe ,实际的界限会是0xffffefff,也就是后面加上fff
						mov ecx, 0x00c09600 ;dpl = 0
						call selector_sys_routine:make_seg_descriptor
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt
						and cx, 1111_1111_1111_1_1_00B
						;or cx, 0000_0000_0000_0_1_00B ;rpl = 0
						mov [esi + 0x22], cx ;0特权级栈的选择子填写到tcb中

						;创建1特权级的栈
						mov ecx, 4096
						mov dword [esi + 0x28], 1 ;1特权级栈的大小(以4kb为单位)填写到tcb中
						call selector_sys_routine:allocate_memory
						add ecx, 4096 ;栈的高端地址
						mov [esi + 0x2c], ecx ;1特权级栈的地址填写到tcb中
						mov dword [esi + 0x32], 0 ;1特权级栈初始的esp填写到tcb中
						mov eax, ecx ;基地址
						mov ebx, 0xfffff
						sub ebx, 1 ;0xffffe ,实际的界限会是0xffffefff,也就是后面加上fff
						mov ecx, 0x00c0b600 ;dpl = 1
						call selector_sys_routine:make_seg_descriptor
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt
						and cx, 1111_1111_1111_1_1_00B
						or cx, 0000_0000_0000_0_1_01B ;rpl = 1
						mov [esi + 0x30], cx ;1特权级栈的选择子填写到tcb中

						;创建2特权级的栈
						mov ecx, 4096
						mov dword [esi + 0x36], 1 ;2特权级栈的大小(以4kb为单位)填写到tcb中
						call selector_sys_routine:allocate_memory
						add ecx, 4096 ;栈的高端地址
						mov [esi + 0x3a], ecx ;2特权级栈的地址填写到tcb中
						mov dword [esi + 0x40], 0 ;2特权级栈初始的esp填写到tcb中
						mov eax, ecx ;基地址
						mov ebx, 0xfffff
						sub ebx, 1 ;0xffffe ,实际的界限会是0xffffefff,也就是后面加上fff
						mov ecx, 0x00c0d600 ;dpl = 2
						call selector_sys_routine:make_seg_descriptor
						mov ebx, esi ;tcb的基地址
						call selector_core_code:fill_descriptor_in_ldt
						and cx, 1111_1111_1111_1_1_00B
						or cx, 0000_0000_0000_0_1_10B ;rpl = 2
						mov [esi + 0x3e], cx ;2特权级栈的选择子填写到tcb中

						;创建并安装ldt描述符
						mov eax, [esi + 0x0c] ;ldt的基地址
						movzx ebx, word [esi + 0x0a] ;ldt的界限
						mov ecx, 0x00008200 ;8 = 1000, 2 = 0010, dpl = 0
						call selector_sys_routine:make_seg_descriptor
						call selector_sys_routine:set_up_gdt_descriptor
						mov [esi + 0x10], cx ;将ldt的选择子填写到tcb中

						;创建tss
						mov ecx, 104 ;104是tss的最小大小，不包含IO许可位串
						call selector_sys_routine:allocate_memory
						mov [esi + 0x14], ecx ;tss的基地址写入到tcb中
						mov edi, ecx
						mov word [esi + 0x12], 103 ;tss的界限写入tcb中

						;填写tss相关的属性，edi中存放的是tss的基地址。属性的来源都是从tcb中来的
						mov word [edi], 0
						mov eax, [esi + 0x24] ;esp0
						mov [edi + 4], eax
						mov ax, [esi + 0x22] ;ss0
						mov [edi + 8], ax
						mov eax, [esi + 0x32] ;esp1
						mov [edi + 12], eax
						mov ax , [esi + 0x30] ;ss1
						mov [edi + 16], ax 
						mov eax, [esi + 0x40] ;esp2
						mov [edi + 20], eax
						mov ax, [esi + 0x3e] ;ss2
						mov [edi + 24], ax
						mov ax, [esi + 0x10] ;ldt的选择子
						mov [edi + 96], ax
						mov word [edi + 102], 103 ;IO映射基地址，这里填的是段界限,表明没有IO许可位串
						mov word [edi + 100], 0

						;填写tss中的eip, cs, ds, es, fs, gs等信息
						mov esi, [esi + 0x06] ;用户程序的基地址
						mov eax, 0
						mov [edi + 28], eax ;填写tss中的CR3
						mov eax, [esi + 0x10] 
						mov [edi + 32], eax ;填写tss中的eip
						pushfd
						pop eax
						mov [edi + 36], eax ;填写tss中的eflags,NT位此时等于0
						mov ax, 0
						mov [edi + 72], ax ;填写tss中的es
						mov ax, [esi + 0x14]
						mov [edi + 76], ax ;填写tss中的cs
						mov ax, [esi + 0x08]
						mov [edi + 80], ax ;填写tss中的ss
						mov ax, [esi + 0x04]
						mov [edi + 84], ax ;填写tss中的ds,注意这里的处理，此处使用的是用户程序头部段，而非用户程序数据段
						mov ax, 0
						mov [edi + 88], ax ;填写tss中的fs
						mov ax, 0
						mov [edi + 92], ax ;填写tss中的gs



						;创建并安装tss描述符
						mov esi, [ebp + 11 * 4] ;tcb的基地址
						mov eax, edi
						mov ebx, 103
						mov ecx, 0x00008900 ;dpl = 0
						call selector_sys_routine:make_seg_descriptor
						call selector_sys_routine:set_up_gdt_descriptor
						mov [esi + 0x18], cx ;tss选择子填写到tcb中

						
						pop es
						pop ds
						popad
						ret 8





;-------------------------------------------------------------------------------
fill_descriptor_in_ldt:                     ;在LDT内安装一个新的描述符
                                            ;输入：EDX:EAX=描述符
                                            ;          EBX=TCB基地址
                                            ;输出：CX=描述符的选择子

                        push ds
                        push esi

                        mov esi, selector_0_4_gb
                        mov ds, esi
                        mov esi, [ebx + 0x0c] ;ldt的基地址，4个字节
                        mov cx, [ebx + 0x0a] ;ldt的界限，2个字节
                        inc cx
                        and ecx, 0x0000ffff
                        mov [esi + ecx], eax ;安装描述符低位到ldt中
                        mov [esi + ecx + 4], edx ;安装描述符高位到ldt中

                        add ecx, 8
                        dec ecx
                        mov [ebx + 0x0a], cx ;把新的界限回写到tcb中，2个字节

                        mov eax, ecx
                        mov edx, 0
                        mov ecx, 8
                        div ecx
                        mov ecx, eax ;eax中存放的是索引
                        shl ecx, 3 ;索引左移3位
                        or cx, 0000_0000_0000_0_1_11B ;创建描述符的选择子，位于ldt中，rpl是3

                        pop esi
                        pop ds
                        retf




;-------------------------------------------------------------------------------
append_to_tcb_link:                         ;在TCB链上追加任务控制块
                                            ;输入：ECX=TCB线性基地址   

                        push ds
                        push es
                        push eax 
                        push esi

                        mov eax, selector_core_data
                        mov ds, eax
                        mov eax, selector_0_4_gb
                        mov es, eax

                        mov eax, 0
                        mov [es:ecx], eax ;下一个tcb的基地址设置为0，表示没有下一个tcb了

                        mov esi, [tcb_chain]
                        cmp esi, 0
                        je attl_1
                
attl_3:                 mov eax, [es:esi]
                        cmp eax, 0
                        jne attl_2
                        mov [es:esi], ecx
                        jmp attl_ret

attl_2:                 mov esi, eax
                        jmp attl_3

attl_1:                 mov [tcb_chain], ecx 

;这是一个技巧，返回的这里也可以加上标签
attl_ret:               pop esi
                        pop eax
                        pop es
                        pop ds
                        ret                                                           



;-------------------------------------------------------------------------------
;用户程序的返回点
return_point:
						mov eax, selector_core_data
						mov ds, eax
						mov eax, selector_core_stack
						mov ss, eax
						mov ebx, msg_ret_from_user_program
						call selector_sys_routine:put_string
						hlt



;-------------------------------------------------------------------------------
;核心代码段的入口
start:	
						;获取cpu信息
						mov eax, 0
						cpuid
						cmp eax, 4
						jng load_user_program


;输出cpu的品牌信息
out_brand:				
						mov eax, selector_core_data
						mov ds, eax

						mov eax, 0x80000002
						cpuid
						mov [cpu_brand + 0], eax
						mov [cpu_brand + 4], ebx
						mov [cpu_brand + 8], ecx
						mov [cpu_brand + 12], edx

						mov eax, 0x80000003
						cpuid
						mov [cpu_brand + 16], eax
						mov [cpu_brand + 20], ebx
						mov [cpu_brand + 24], ecx
						mov [cpu_brand + 28], edx

						mov eax, 0x80000004
						cpuid
						mov [cpu_brand + 32], eax
						mov [cpu_brand + 36], ebx
						mov [cpu_brand + 40], ecx
						mov [cpu_brand + 44], edx

						mov ebx, cpu_brand
						call selector_sys_routine:put_string

						;生成并安装调用门描述符
load_user_program:		mov ecx, [salt_items]
						mov esi, salt_1
install_gate_1:			mov eax, [esi + 256] ;偏移地址
						mov bx, [esi + 260] ;选择子
						push ecx
						mov cx, 1_11_0_1100_000_00000B ;属性，dpl = 3
						call selector_sys_routine:make_gate_descriptor
						call selector_sys_routine:set_up_gdt_descriptor
						mov [esi + 260], cx ;将调用门的选择子回填
						pop ecx
						add esi, 262
						loop install_gate_1

						;测试调用门描述符是否安装成功
						mov ebx, gate_install_success
						call far [salt_1 + 256] ;偏移将被忽略


						;创建核心任务
						mov ecx, selector_0_4_gb
						mov es, ecx
						mov ecx, 104
						call selector_sys_routine:allocate_memory
						mov [prgman_tss], ecx
						mov word [es:ecx], 0 ;前一个任务的tss选择子
						mov dword [es:ecx + 28], 0 ;cr3
						mov word [es:ecx + 96], 0 ;ldt选择子
						mov word [es:ecx + 102], 103 ;IO映射基地址

						;生成并安装核心任务tss描述符
						mov eax, [prgman_tss]
						mov ebx, 103
						mov ecx, 0x00008900 ;属性，dpl = 0
						call selector_sys_routine:make_seg_descriptor
						call selector_sys_routine:set_up_gdt_descriptor
						mov [prgman_tss + 0x04], cx

						;加载tr寄存器, 加载之后代表核心任务已经开始存在并运行了
						ltr cx

						;第一次加载用户程序
						;创建tcb，将tcb添加到tcb链中
						mov ecx, 0x46
						call selector_sys_routine:allocate_memory ;输出：ecx起始线形基地址
						call append_to_tcb_link ;输入：ECX=TCB线性基地址

						;调用加载并重定位用户程序的子程序
						push dword user_program_sector ;用户程序在硬盘上面的逻辑扇区号
						push ecx ;tcb的基地址
						call load_relocate_program

						;通过任务切换的方式进入用户程序,call的方式
						mov ebx, prgman_msg1
						call selector_sys_routine:put_string
						call far [es:ecx + 0x14] ;tcb中的0x14开始的6个字节分别是tss基地址和tss选择子




						;第二次加载用户程序
						;创建tcb，将tcb添加到tcb链中
						mov ecx, 0x46
						call selector_sys_routine:allocate_memory ;输出：ecx起始线形基地址
						call append_to_tcb_link ;输入：ECX=TCB线性基地址

						;调用加载并重定位用户程序的子程序
						push dword user_program_sector ;用户程序在硬盘上面的逻辑扇区号
						push ecx ;tcb的基地址
						call load_relocate_program

						;通过任务切换的方式进入用户程序,jmp的方式
						mov ebx, prgman_msg2
						call selector_sys_routine:put_string
						jmp far [es:ecx + 0x14] ;tcb中的0x14开始的6个字节分别是tss基地址和tss选择子


						;从用户程序切换回来之后，打印信息，并停止
						mov ebx, prgman_msg3
						call selector_sys_routine:put_string
						hlt

						

						


;==================================================核心代码段结束==================================================





section tail
end_: 
